import Data.List (repeat)

data Grid = Floor | EmptySeat | OccupiedSeat deriving (Eq,Show)

type Position = (Int,Int)
data Layout = Layout [[Grid]] (Int,Int) deriving (Show, Eq)

type Direction = (Int,Int)
{-
    (-1,-1) (-1, 0) (-1, 1)
    (0 ,-1)         (0,  1)
    (1 ,-1) (1,  0) (1,  1)
-}

parseGrid :: Char -> Grid
parseGrid ch = case ch of
    '.' -> Floor
    'L' -> EmptySeat
    '#' -> OccupiedSeat

parseInput :: String -> Layout
parseInput string = 
    Layout grids (rowCount,colCount)
    where
        stringLines = lines string
        grids = map (map parseGrid) stringLines

        rowCount = length grids
        colCount = length . head $ grids

getMaybeGrid :: Layout -> Position -> Maybe Grid
getMaybeGrid (Layout grids (rowCount,colCount)) (row,col) 
    | row < 0 || row >= rowCount = Nothing
    | col < 0 || col >= colCount = Nothing
    | otherwise =  Just $ (grids !! row) !! col

adjacentGrid :: Layout -> Position -> Direction -> Grid
adjacentGrid layout@(Layout grids (rowCount,colCount)) (row,col) dir@(dr,dc) = 
    case grid' of 
        Nothing -> Floor
        Just grid -> 
            if grid /= Floor 
                then grid 
                else adjacentGrid layout nextPos dir
    where
        nextPos = (row+dr, col+dc)
        grid' = getMaybeGrid layout nextPos

adjacentGrids :: Layout -> Position -> [Grid]
adjacentGrids layout pos = map (adjacentGrid layout pos) [(-1,-1),(-1, 0),(-1, 1),
                                                          (0 ,-1),        (0,  1),
                                                          (1 ,-1),(1,  0),(1,  1)]

obeyEmptySeatRule :: Layout -> Position -> Bool
obeyEmptySeatRule layout pos =
    (all (/=OccupiedSeat)) $ adjacentGrids layout pos

obeyOccupiedSeatRule :: Layout -> Position -> Bool
obeyOccupiedSeatRule layout pos = 
    ((>=5) . length . filter (==OccupiedSeat)) $ adjacentGrids layout pos

gridWithPosition :: Layout -> [[(Position,Grid)]]
gridWithPosition (Layout grids (row,col)) = posgrids
    where
        positions = map (\r -> zip (repeat r) [0..(col-1)]) [0..(row-1)]
        posgrids = map (\(p,g) -> zip p g) (zip positions grids)

changeGrid :: Layout -> (Position,Grid) -> Grid
changeGrid layout (pos,grid) 
    | grid == Floor = Floor
    | grid == EmptySeat = if obeyEmptySeatRule layout pos then OccupiedSeat else EmptySeat
    | grid == OccupiedSeat = if obeyOccupiedSeatRule layout pos then EmptySeat else OccupiedSeat

applyRule :: Layout -> Layout
applyRule layout@(Layout grids (row,col)) = 
    Layout newGrids (row,col)
    where
        posgrids = gridWithPosition layout
        newGrids = map (map (changeGrid layout)) posgrids

loopApply :: Layout -> Layout
loopApply layout = 
    if newLayout == layout 
        then newLayout
        else loopApply newLayout
    where
        newLayout = applyRule layout

occupiedSeatCount :: Layout -> Int
occupiedSeatCount (Layout grids _) = 
    sum . map (length . filter (==OccupiedSeat)) $ grids

main = do
    input <- readFile "input.txt"
    print . occupiedSeatCount . loopApply . parseInput $ input
    